﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using System.Windows.Forms;
using TouchRC.Classes;
using TouchRC.Logging;

namespace TouchRC.Logic
{
    // State object for receiving data from remote device.
    public class StateObject
    {
        // Client socket.
        public Socket workSocket = null;
        // Size of receive buffer.
        public const Int32 BufferSize = 512;
        // Receive buffer.
        public byte[] buffer = new byte[BufferSize];
        // Received data String.
        public StringBuilder sb = new StringBuilder();
    }

    public class AsynchronousClient
    {
        // The port number for the remote device.
        private static Int32 m_RemotePort;
        private static Int32 m_ListeningPort;
        private static IPAddress m_Address;

        public static Boolean Init(ComputerListBoxItem compy)
        {
            string a = compy.DnsAddress;
            try
            {
                while (a.StartsWith("\\"))
                {
                    a = a.Substring(1);
                }
                IPHostEntry bob = Dns.GetHostEntry(a);
                foreach (IPAddress addy in bob.AddressList)
                {
                    if (addy.AddressFamily == AddressFamily.InterNetwork)
                    {
                        m_Address = addy;
                    }
                }
                if (m_Address == null)
                    throw new Exception();
            }
            catch
            {
                MessageBox.Show("Computer adress not valid.");
                return false;
            }

            m_RemotePort = compy.RemotePort;
            if (m_RemotePort <= 0)
            {
                MessageBox.Show("Port nr. er ikke korrekt");
                return false;
            }

            m_ListeningPort = compy.ListeningPort;
            if (m_ListeningPort <= 0)
            {
                MessageBox.Show("Port nr. er ikke korrekt");
                return false;
            }

            return true;

        }
        public static string OpenAndSendRequest(String Command)
        {
            AsynchronousClient Comm = new AsynchronousClient();
            Comm.SendRequest(Command);
            return Comm.response;
        }
        public static string OpenAndReceive()
        {
            AsynchronousClient Comm = new AsynchronousClient();
            Comm.ReceiveInfo();
            return Comm.response;
        }

        private void ReceiveInfo()
        {
            IPAddress ipAddress = new IPAddress(m_Address.GetAddressBytes());
            IPEndPoint remoteEP = new IPEndPoint(ipAddress, m_ListeningPort);

            // Create a TCP/IP socket.
            Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            // Connect to the remote endpoint.
            try
            {
                client.Connect(remoteEP);
            }
            catch
            {
                response = "ERROR - Connection could not be established.";
                return;
            }

            // Receive the response from the remote device.
            try
            {
                receiveDone.Reset();
                Receive(client);
                receiveDone.WaitOne();
            }
            catch
            {
                response = "ERROR - No reply from server.";
            }

            // Release the socket.
            client.Shutdown(SocketShutdown.Both);
            client.Close();
        }

        public void OpenConnection()
        {
            Socket socket;
            try
            {
                // Create New Socket 
                socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                // Create New EndPoint
                IPEndPoint iep = new IPEndPoint(m_Address, m_RemotePort);
                // This is a non blocking IO
                socket.Blocking = true;
                // Assign Callback function to read from Asyncronous Socket
                AsyncCallback callbackProc = new AsyncCallback(ConnectCallback);
                // Begin Asyncronous Connection

                StateObject state = new StateObject();
                state.workSocket = socket;

                connectDone.Reset();
                socket.BeginConnect(iep, callbackProc, socket);
                connectDone.WaitOne();
            }
            catch (Exception ers)
            {
                Console.WriteLine(ers.ToString());
            }
        }

        // ManualResetEvent instances signal completion.
        private ManualResetEvent connectDone = new ManualResetEvent(false);
        private ManualResetEvent sendDone = new ManualResetEvent(false);
        private ManualResetEvent receiveDone = new ManualResetEvent(false);

        // The response from the remote device.
        public String response = String.Empty;

        public void SendRequest(String Command)
        {
            // Establish the remote endpoint for the socket.
            IPAddress ipAddress = new IPAddress(m_Address.GetAddressBytes());
            IPEndPoint remoteEP = new IPEndPoint(ipAddress, m_RemotePort);

            // Create a TCP/IP socket.
            Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            // Connect to the remote endpoint.
            try
            {
                client.Connect(remoteEP);
            }
            catch
            {
                response = "ERROR - A connection could not be made with the server.";
                return;
            }

            if (!Command.EndsWith("\r\n"))
                Command += "\r\n";
            // Send data to the remote device.
            Send(client, Command);

            // Receive the response from the remote device.
            try
            {
                receiveDone.Reset();
                Receive(client);
                receiveDone.WaitOne();
            }
            catch
            {
                response = "ERROR - No reply from the server.";
            }

            // Release the socket.
            client.Shutdown(SocketShutdown.Both);
            client.Close();
        }

        private void ConnectCallback(IAsyncResult ar)
        {
            // Retrieve the socket from the state object.
            Socket client = (Socket)ar.AsyncState;

            // Complete the connection.
            client.EndConnect(ar);

            // Signal that the connection has been made.
            connectDone.Set();
        }

        private void Receive(Socket client)
        {
            try
            {
                // Create the state object.
                StateObject state = new StateObject();
                state.workSocket = client;

                // Begin receiving the data from the remote device.
                client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                    new AsyncCallback(ReceiveCallback), state);
            }
            catch(Exception e)
            {
                Logger.LogMessage("Error receiving from from server: {0}", e.Message);
            }
        }

        private void ReceiveCallback(IAsyncResult ar)
        {
            // Retrieve the state object and the client socket 
            // from the asynchronous state object.
            StateObject state = (StateObject)ar.AsyncState;
            Socket client = state.workSocket;

            int bytes = 0;
            try
            {
                // Read data from the remote device.
                Int32 bytesRead = client.EndReceive(ar);
                bytes = bytesRead;
            }
            catch (SocketException up)
            {
                response = "ERROR - No reply from server.";
            }
            if (bytes > 0)
            {
                // There might be more data, so store the data received so far.
                state.sb.Append(Encoding.Default.GetString(state.buffer, 0, bytes));
            }

            string bob = string.Empty;
            if (state.sb.Length > 1)
            {
                bob = state.sb.ToString();

            }
            response += bob;
            if (bob.EndsWith("200 Ok\r\n"))
            {
                client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                    new AsyncCallback(ReceiveCallback), state);
            }
            else
            {
                receiveDone.Set();
            }
        }

        private void Send(Socket client, String data)
        {
            // Convert the String data to byte data using UTF7 encoding.
            byte[] byteData = Encoding.Default.GetBytes(data);

            // Begin sending the data to the remote device.
            client.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallback), client);
        }

        private void SendCallback(IAsyncResult ar)
        {

            // Retrieve the socket from the state object.
            Socket client = (Socket)ar.AsyncState;

            // Complete sending the data to the remote device.
            Int32 bytesSent = client.EndSend(ar);

            // Signal that all bytes have been sent.
            sendDone.Set();
        }
    }
}
